﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Fire2.cs" company="The BomberJoe Team">
//   Author: Søren Lillethorup Vange (slil@itu.dk)
// </copyright>
// <summary>
//   A fire that will spread in it's direction until its range is 0 or it hits a Top.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace BomberGame
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Graphics;

    /// <summary>
    /// A fire that will spread in one to four directions.
    /// The default is 1 or 4 directions with an identical fire range
    /// but it can add more with different fire range to the first if it is hit by another fire.
    /// </summary>
    public class Fire : Top
    {
        /// <summary>
        /// A dictionary that maps the possible directions to the InnerFire that will spread in that direction.
        /// </summary>
        private readonly Dictionary<Direction, InnerFire> _fires = new Dictionary<Direction, InnerFire>();

        /// <summary>
        /// Initializes a new instance of the <see cref="Fire"/> class.
        /// </summary>
        /// <param name="range">
        /// The range.
        /// </param>
        /// <param name="owner">
        /// The owner.
        /// </param>
        /// <param name="direction">
        /// The direction the fire will spread.
        /// </param>
        /// <param name="texture">
        /// The texture.
        /// </param>
        public Fire(uint range, Player owner, Direction direction = null, Texture2D texture = null)
            : base(texture, true, owner)
        {
            Contract.Requires(texture != null || DefaultTexture != null);
            Contract.Requires(CrossTexture != null);
            Contract.Requires(EndTexture != null);
            if (direction == null)
            {
                if (texture == null)
                {
                    Texture = DefaultTexture;
                }

                foreach (var dir in Direction.GetEnumerable())
                {
                    this._fires[dir] = new InnerFire(range, owner);
                }
            }
            else
            {
                _fires[direction] = new InnerFire(range, owner);

                if (texture == null)
                {
                    Texture = DefaultTexture;
                } 
            }
        }

        /// <summary>
        /// Gets or sets the fire's sound effect.
        /// </summary>
        /// <value>
        /// The sound effect.
        /// </value>
        public static SoundEffect FireSFX { private get; set; }

        /// <summary>
        /// Gets or sets the default texture.
        /// </summary>
        /// <value>
        /// The default texture.
        /// </value>
        public static Texture2D DefaultTexture { private get; set; }

        /// <summary>
        /// Gets or sets the cross texture.
        /// </summary>
        /// <value>
        /// The cross texture.
        /// </value>
        public static Texture2D CrossTexture { private get; set; }

        /// <summary>
        /// Gets or sets the end texture.
        /// </summary>
        /// <value>
        /// The end texture.
        /// </value>
        public static Texture2D EndTexture { private get; set; }

        /// <summary>
        /// Fire damages a player that tries to move into it.
        /// </summary>
        /// <param name="player">
        /// The player that entered.
        /// </param>
        public override void Enter(Player player)
        {
            player.Damage(Owner);
        }

        /// <summary>
        /// Updates the fire and spreads it to neighbours if its time has come.
        /// </summary>
        /// <param name="gameTime">
        /// The game time.
        /// </param>
        public override void Update(GameTime gameTime)
        {
            var directionsToRemove = new List<Direction>(4);

            // Update all InnerFire's and mark them for removal if they report back negativ.
            foreach (var keyPair in this._fires)
            {
                bool alive = keyPair.Value.Update(Stage.CurrentStage.GetBottom(GridVector), keyPair.Key);
                if (!alive)
                {
                    directionsToRemove.Add(keyPair.Key);
                }
            }

            // Remvoe alle dead InnerFire's
            foreach (var direction in directionsToRemove)
            {
                this._fires.Remove(direction);
            }

            // Remove itself if there are no more InnerFire's
            if (this._fires.Count == 0)
            {
                Stage.CurrentStage.RemoveTop(this);
            }
        }

        /// <summary>
        /// Draws the Fire.
        /// The flame is turned in its direction.
        /// </summary>
        /// <param name="batch">
        /// The batch.
        /// </param>
        public override void Draw(SpriteBatch batch)
        {
            if (this._fires.Count == 0)
            {
                // Is not a contract as draw is called no matter what in a context we do not have full control over.
                throw new Exception("No more fire to show. Should have been removed");
            }

            Texture2D tex;
            var rect = PixelRect;
            float rotation = 0;

            if (this._fires.Count > 1 && !(this._fires.Count == 2 && this._fires.First().Key.Axis == this._fires.Last().Key.Axis))
            {
                tex = CrossTexture;
            }
            else
            {
                Direction direction;
                if (this._fires.Count == 2)
                {
                    direction = this._fires.First().Key;
                    tex = Texture;
                }
                else
                {
                    var keyPair = this._fires.Single();
                    direction = keyPair.Key;
                    tex = keyPair.Value.FireLeft == 0 ? EndTexture : Texture;
                }

                // If the direction is not North we will have to turn the texture and set an offset on the rect
                // So that it is drawn in the correct place.
                if (direction == Direction.East)
                {
                    rect.Offset(Scale, 0);
                    rotation = MathHelper.ToRadians(90f);
                }
                else if (direction == Direction.South)
                {
                    rect.Offset(Scale, Scale);
                    rotation = MathHelper.ToRadians(2 * 90f);
                }
                else if (direction == Direction.West)
                {
                    rect.Offset(0, Scale);
                    rotation = MathHelper.ToRadians(3 * 90f);
                }
            }

            batch.Draw(tex, rect, null, Color.White, rotation, Vector2.Zero, SpriteEffects.None, 0f);
        }

        /// <summary>
        /// A fire that will spread in it's direction until its range is 0 or it hits a non fire Top.
        /// </summary>
        private class InnerFire
        {
            /// <summary>
            /// The distance it has left to spread.
            /// </summary>
            public readonly uint FireLeft;

            /// <summary>
            /// The owner of this fire.
            /// </summary>
            private readonly Player _owner;

            /// <summary>
            /// The number of updates a fire will live.
            /// </summary>
            private const int Lifespan = 30;

            /// <summary>
            /// Counts the number of updates the Fire has lived in.
            /// </summary>
            private uint _accumulator;

            /// <summary>
            /// Initializes a new instance of the <see cref="InnerFire"/> class.
            /// </summary>
            /// <param name="fireLeft">
            /// The fire left to spread.
            /// </param>
            /// <param name="owner">
            /// The owner.
            /// </param>
            public InnerFire(uint fireLeft, Player owner)
            {
                _accumulator = 0;
                this.FireLeft = fireLeft;
                this._owner = owner;

                FireSFX.Play();
            }

            /// <summary>
            /// Updates the state of the Fire.
            /// The second time it is called it damages the neighbour in its direction and
            /// then adds a fire if there were no top and it is allowed to have a fire.
            /// In the update that corresponds to its life time it will remove itself.
            /// </summary>
            /// <param name="bottom">
            /// The bottom.
            /// </param>
            /// <param name="direction">
            /// The direction.
            /// </param>
            /// <returns>
            /// The false if the object is dead and should be removed
            /// </returns>
            public bool Update(Bottom bottom, Direction direction)
            {
                switch (this._accumulator)
                {
                    case 1:
                        if (this.FireLeft != 0)
                        {
                            this.SpreadFire(bottom.GetNeighbour(direction), direction);
                        }

                        break;
                    case Lifespan:
                        return false;
                }

                _accumulator++;
                return true;
            }

            /// <summary>
            /// Spreads the fire to the given neighbour in the given direction
            /// </summary>
            /// <param name="neighbour">
            /// The neighbour.
            /// </param>
            /// <param name="direction">
            /// The direction.
            /// </param>
            private void SpreadFire(Bottom neighbour, Direction direction)
            {
                Contract.Requires(this.FireLeft != 0);
                neighbour.Damage(this._owner);
                var newFireRange = this.FireLeft - 1;

                // If the neighbour is empty and allowed to hold a top.
                if (neighbour.Passable && !Stage.CurrentStage.ContainsTop(neighbour))
                {
                    var fire2 = new Fire(newFireRange, this._owner, direction);
                    Stage.CurrentStage.AddTop(fire2, neighbour.GridVector);
                }
                else if (neighbour.Passable)
                {
                    // If the neighbour is passable but has a top get that top.
                    var top = Stage.CurrentStage.GetTop(neighbour);
                    if (top is Fire)
                    {
                        var fire = (Fire)top;

                        // Change the owner as this fire was the last that hit this bottom.
                        fire.Owner = this._owner;
                        if (!fire._fires.ContainsKey(direction) || fire._fires[direction].FireLeft < newFireRange)
                        {
                            // If the fire does not contain an inner fire in the direction or if it has a lower range than the new range 
                            fire._fires[direction] = new InnerFire(newFireRange, this._owner);
                        }
                    }
                }
            }
        }
    }
}
